/*******************************************************************************
 * \brief 创建并用软件生成BMP文件；修改文件中像素内容。
 * \details File format: UTF-8
 *       可以用GCC、Qt、VS编译并运行
 * \author 将狼才鲸
 * \date 2022-10-08
 * \warning 此处我默认你的目标运行环境是32位cpu！一个int类型4个字节；
 *          编译环境是类似于Linux/GCC、Windows这样的小端模式
 * \note 暂时还没有写Makefile文件，如果在Linux或者MinGW下编译直接使用命令
         gcc -o demo main.c，已经能成功生成灰色图片
 * \note 参考网址：
 *       [BMP文件格式详解（BMP file format）](https://blog.csdn.net/qq_41137110/article/details/119893817)
 *       [BMP文件格式解析](https://blog.csdn.net/XYK0318/article/details/105123197)
 ******************************************************************************/

/**
 * BMP文件格式为文件头（指示文件大小和像素起始位置）+信息头（指示颜色表长度和像素内容长度）+变长颜色表+变长像素内容，BMP存储的内容是小端模式，和GCC中一样。像素中颜色通道的顺序是蓝、绿、红
 */

/************************************ 头文件 ***********************************/
#include <string.h> /* memset, strcmp */
#include <stdio.h> /* fopen, fwrite, fclose, fflush, printf */
#include <stdlib.h> /* malloc free */

/************************************ 宏定义 ***********************************/
/* 自动生成BMP图片时的默认参数 */
#define WIDTH 1280
#define HEIGHT 720
#define BIT_DEPTH 3 /* 3 x 8 = 24bits */
#define DEFAULT_BMP_FILE_NAME "picture.bmp"

/* 简单的参数处理宏定义，真正正式的参数处理比这个要复杂的多，是一个大的模块 */
#define BMP_HELP_STR "-h"
#define BMP_CREATE_NEW "-n"
#define BMP_PRINT "-p"
#define BMP_COMPARE "-c"
#define BMP_MERGE "-m"
#define BIN_YUV_COMPARE "-b"

/*********************************** 全局变量 **********************************/
/* 大数组空间需要开辟在函数外面，避免超出芯片的堆栈；多开一点冗余空间方便每行4字节对齐 */
static char image_data[(WIDTH + 4) * HEIGHT * BIT_DEPTH];

/************************************ 结构体 ***********************************/
/* 当前程序支持的操作 */
enum {
	BMP_HELP = 0,			/* 打印帮助信息 */
	BMP_CREATE_DEFAULT,		/* 手动生成BMP文件 */
	BMP_PRINT_INFO,			/* 读取一个BMP文件，判断是否正确，并打印图片信息 */
	BMP_COMPARE_2_FILES,	/* 比较两个BMP文件，打印差别信息，保存差别图片 */
	BMP_MERGE_2_FILES,		/* 合并两个BMP文件为一个，并保存 */
	BIN_YUV_COMPARE_2_FILES,	/* 比较两个二进制文件，如YUV文件，打印差别信息，保存差别文件 */
};

/**
 * \brief BMP文件头，14字节
 */
typedef struct _bitmap_file_header {
	short int type;	/* 类型，内容为BM这两个字母 */
	int size; 	/* 文件大小，单位字节 */
	short int rsv1;	/* 保留 */
	short int rsv2; /* 保留 */
	int off_bits;	/* 位图像素数据相对于文件头的字节偏移量 */
} __attribute__ ((packed)) BITMAP_FILE_HEADER_T; /* 取消字节对齐（也就是1字节对齐） */

/**
 * \brief BMP信息头，常用40字节，其它不常用的还有12、64、108、124字节
 */
typedef struct _bitmap_info_header {
	int size;	/* 本结构体占的字节数 */
	int width;	/* 位图宽度，单位是像素 */
	int height;	/* 位图高度，单位像素 */
	short int planes;	/* 目标设备级别，值必须为1 */
	short int bit_count;	/* 每个像素的位数；1：双色，4：16色，8：256色，24：真彩色，32：增强真彩色，现在一般是32位色；
                                   当值为1、4、8时默认颜色表中颜色有2、16、256个；当为24、32时没有颜色表*/
	int compression;	/* 位图压缩类型；当前必须为0：不压缩，其它的还有1：BI_RLE8或BI_RLE4 */
	int size_image;	/* 实际位图像素数据所有的字节数，包括4字节对齐后补齐的部分，不是像素数 */
	int x_pels_per_meter;	/* 水平分辨率，每米像素数 */
	int y_pels_per_meter;	/* 垂直分辨率，每米像素数 */
	int clr_used;	/* 位图实际使用的颜色表中的颜色数；现在位图基本上都没有颜色表了，都是真彩色 */
	int clr_important;	/* 位图显示过程中重要的颜色数 */
} __attribute__ ((packed)) BITMAP_INFO_HEADER_T;

/**
 * 像素阵列记录顺序是像素从左往右，然后行数从上往下，一行字节数必须是4的倍数，不足的以字节0补齐
 */

/**
 * \brief 颜色表中一个元素的结构体
 */
typedef struct _rgb_quad {
	char rgb_blue;
	char rgb_green;
	char rgb_red;
	char rgb_rsv;	/* 保留，必须为0 */
} __attribute__ ((packed)) RGB_QUAD_T;

/*********************************** 内部函数 **********************************/
/**
 * \brief 自动生成图片的颜色类型
 */
typedef enum _bmp_test_image_color_type {
	GREY = 0, /* 纯灰色图片 */
	WHITE,
	BLACK,
	RED,
	GREEN,
	BLUE,
	BLACK_STRIPE, /* 黑色斜条纹 */
	RED_STRIPE,
	GREEN_STRIPE,
	BLUE_STRIPE
} BMP_TEST_IMAGE_COLOR_TYPE_T;

/**
 * \brief 生成一张指定颜色的BMP图片
 * \warning 切换不同颜色的代码还未添加
 * \note 为了软件处理简单，传入的文件名请带上.bmp后缀
 */
static int create_test_bmp(int type, char *fname)
{
	/* 手动准备好默认参数 */
	int bit_depth = BIT_DEPTH * 8; /* 现在都是32位图片了 */
	int width = WIDTH;
	int height = HEIGHT;
	int info_header_size = sizeof(BITMAP_INFO_HEADER_T);
	char *file_name = DEFAULT_BMP_FILE_NAME;

	if (fname)
		file_name = fname;

	/** 手动生成一张BMP图片 */
	/* 1. 填写文件头 */
	BITMAP_FILE_HEADER_T file_header;
	memset(&file_header, 0, sizeof(file_header)); /* 内容先清零，这样保留的值就不用手动填，也避免有些值忘记填产生异常 */
	/* 文件大小和像素偏移最后再填，先只填type */
	file_header.type = ('M' << 8) | 'B'; /* GCC默认是小端模式，所以BM这两个字节要处理下 */

	/* 2. 填写信息头 */
	BITMAP_INFO_HEADER_T info_header;
	memset(&info_header, 0, sizeof(info_header));
	info_header.size = info_header_size;
	info_header.width = width;
	info_header.height = height;
	info_header.planes = 1;
	info_header.bit_count = bit_depth;
	info_header.compression = 0;
	info_header.size_image =
		((width % 4) ? ((width / 4) * 4 + 4) : width) * height * (bit_depth / 8);
	info_header.x_pels_per_meter = 2800;
	info_header.y_pels_per_meter = 2800;

	/* 3. 补全文件头内容 */
	if (info_header.bit_count >= 24) {
		file_header.size = sizeof(file_header) + sizeof(info_header)
			+ info_header.size_image;
		file_header.off_bits = sizeof(file_header) + sizeof(info_header);
	} else {
		printf("bit depth %d not support!\n", info_header.bit_count);
		return 0;
	}

	/* 3. 打开文件并写BMP文件头 */
	FILE *fp = fopen(file_name, "wb+"); /* 替换或者新建文件，用二进制读写 */
	if (fp == NULL) {
		printf("create file %s fail!\n", file_name);
		return 0;
	}

	/* 4. 手动填写图片像素，先都弄成灰色 */
	int image_length = info_header.size_image;
	for (int i = 0; i < image_length; i++) {
		/**
		 * \todo 此处先不考虑宽没有4字节对齐，需要补零的情况
		 * \warning 测试时一定先手动保证宽度4字节对齐
		 */
		image_data[i] = 0x7F;
	}

	/**
	 * fwrite和fread的第2个参数（也就是第1个长度）是你单次写的长度；
	 * 比如是char数组，那么单次写1字节比较好；如果是int数组，单次写4字节比较好；
	 * 第2个长度是写多少个单元，总长度是长度1 x 长度2
	 */
	fwrite(&file_header, sizeof(file_header), 1, fp);
	fwrite(&info_header, sizeof(info_header), 1, fp);

	if (info_header.bit_count < 24) {
		/**
		 * \todo 如果需要，请补充像素位深度为1、4、8的情况
		 */
		printf("err: please add codes parse bit count %d\n", info_header.bit_count);
	}

	/* 第三个参数是每次写几个字节，这里是char数组，所以每次写1个 */
	fwrite(image_data, 1, image_length, fp);
	fflush(fp); /* 把缓存中的内容确定写到文件中，其实这里要fclose了可以不用的 */
	fclose(fp);

	printf("Create BMP picture %s pass.\n", file_name);

	return 0;
}

/**
 * \brief 判断一个BMP文件是否合法，并打印BMP文件信息
 * \details pph_file和pph_info这两个参数，是为了兼容比较两个BMP图片时，
 *          方便获取到结构体信息
 */
static int print_bmp_info(char *fname, BITMAP_FILE_HEADER_T **pph_file,
							BITMAP_INFO_HEADER_T **pph_info)
{
	int ret;
	BITMAP_FILE_HEADER_T file_header;
	BITMAP_INFO_HEADER_T info_header;
	BITMAP_FILE_HEADER_T *ph_file;
	BITMAP_INFO_HEADER_T *ph_info;

	/* 1. 参数检测 */
	if (!fname) {
		printf("err: filename is empty!\n");
		return -1;
	}

	/* 函数后面两个指针的指针的参数，是为了方便比较两个BMP文件时获取结构体信息 */
	if (!pph_file) {
		ph_file = &file_header;
		printf("WARN: the BITMAP_FILE_HEADER_T param is NULL, use default.\n");
	} else {
		ph_file = *pph_file;
		if (!ph_file) {
			ph_file = &file_header;
			printf("ERR: the value of BITMAP_FILE_HEADER_T pointer param is NULL!\n");
		}
	}
	if (!pph_info) {
		ph_info = &info_header;
		printf("WARN: the BITMAP_INFO_HEADER_T param is NULL, use default.\n");
	} else {
		ph_info = *pph_info;
		if (!ph_info) {
			ph_info = &info_header;
			printf("ERR: the value of BITMAP_INFO_HEADER_T pointer param is NULL!\n");
		}
	}

	/* 2. 文件长度检测 */
	FILE *fp = fopen(fname, "rb+"); /* 读写方式打开指定名字的文件，文件不存在则返回空 */
	if (!fp) {
		printf("err: filename %s not exist!\n", fname);
		return -1;
	}

	fseek(fp, 0, SEEK_END);
	int file_len = ftell(fp);
	fseek(fp, 0, SEEK_SET); /* 恢复到起始位置 */
	if (file_len < sizeof(BITMAP_FILE_HEADER_T) + sizeof(BITMAP_INFO_HEADER_T) + 4) {
		fclose(fp);
		printf("err: file length %d illegal!\n", file_len);
		return -1;
	}

	/* 3. 读取文件头 */
	ret = fread(ph_file, sizeof(BITMAP_FILE_HEADER_T), 1, fp);
	if (ret != 1) { /* 如果读取正常，返回值和fread的第3个参数一致 */
		fclose(fp);
		printf("err: file too short: %d(should be %d)!\n", ret, 1);
		return -1;
	}
	
	/* 4. 读取信息头 */
	ret = fread(ph_info, sizeof(BITMAP_INFO_HEADER_T), 1, fp);
	if (ret != 1) { /* 如果读取正常，返回值和fread的第3个参数一致 */
		fclose(fp);
		printf("err: file too short: %d(should be %d)!\n", ret, 1);
		return -1;
	}

	/* 5. 进一步判断文件长度是否正确 */
	if (ph_file->size != file_len)
		printf("err: file length error, want: %d, real: %d\n", ph_file->size, file_len);

	fclose(fp);

	/* 6. 打印图片信息 */
	printf("File name: %s\n", fname);
	printf("File real length: %d\n", file_len);
	printf("BMP should have length: %d\n", ph_file->size);
	printf("File type: %c%c\n", ph_file->type & 0xFF, (ph_file->type >> 8) & 0xFF);
	printf("Image data offset: %d\n", ph_file->off_bits);

	printf("Info header size: %d\n", ph_info->size);
	if (ph_info->size != 40)
		printf("err: current info header size not support!\n", ph_info->size);
	printf("Picture width: %d\n", ph_info->width);
	printf("Pcture height: %d\n", ph_info->height);
	printf("Planes: %d(should be 1)\n", ph_info->planes);
	printf("Compression :%d(should be 0)\n", ph_info->compression);
	printf("Image size: %d\n", ph_info->size_image);
	printf("X ppi: %d\n", ph_info->x_pels_per_meter);
	printf("Y ppi: %d\n", ph_info->y_pels_per_meter);
	printf("Color used: %d\n", ph_info->clr_used);
	if (ph_info->clr_used)
		printf("err: not support color table current!\n");
	printf("Color important: %d\n", ph_info->clr_important);
	if (ph_info->clr_important)
		printf("err: not support color table current!\n");

	return 0;
}

/**
 * \brief 存储两张图片的差值
 * \details 因为一个像素中一个原色的存储范围是0~255无符号型，
 *          所以当前差值只能存储-128到+127，超出范围进行饱和计算，
 *          直接截断，这样差值信息不可避免有失真；
 *          如果差值信息存储0~255无符号型的话，差值到不会丢，
 *          但是会出现反色，而且也不能逆推出正确的数据，
 *          比如1 - 5 = 252
 */
static int image_pixel_compare(char *fname1, char *fname2)
{
#define DIFF_FILE_NAME "diff_picture.bmp"
#define RED_FILE_NAME "red_picture.bmp"
#define GREEN_FILE_NAME "green_picture.bmp"
#define BLUE_FILE_NAME "blue_picture.bmp"
#define USE_SIGNED_CHAR	/* 用有符号只是方便后续计算，并不方便肉眼观看，建议屏蔽掉 */
//#define USE_ABSOLUTE_VALUE	/* 避免颜色翻转，如果有负值则变成绝对值，如果开启了肉眼看颜色会很浅 */
//#define SAVE_RGB_CHANNEL_PICTURE /* 再额外保存红绿蓝三个图片 */
	int ret = 0;
	int i, j;
	FILE *fp1, *fp2, *fp_diff, *fp_red, *fp_green, *fp_blue;
	BITMAP_FILE_HEADER_T file_header1, file_header2;
	BITMAP_INFO_HEADER_T info_header1, info_header2;
	BITMAP_FILE_HEADER_T *ph_file1, *ph_file2;
	BITMAP_INFO_HEADER_T *ph_info1, *ph_info2;

	/* 1. 参数检测 */
	if (!fname1 || !fname2) {
		printf("err: one of filename is empty!\n");
		return -1;
	}

	ph_file1 = &file_header1;
	ph_info1 = &info_header1;
	ph_file2 = &file_header2;
	ph_info2 = &info_header2;

	/**
	 * 之前已经判断过了文件长度、BMP格式，此处省略，无需再次进行异常判断和错误处理
	 */

	/* 1. 读取文件1所有内容 */
	fp1 = fopen(fname1, "rb+");
	fread(ph_file1, sizeof(BITMAP_FILE_HEADER_T), 1, fp1);
	fread(ph_info1, sizeof(BITMAP_INFO_HEADER_T), 1, fp1);
	if (!ph_info1->size_image) /* 比如格式工厂转码的BMP文件就有没有总像素值的问题 */
		ph_info1->size_image = ((ph_info1->width % 4)
			? ((ph_info1->width / 4) * 4 + 4)
			: ph_info1->width) * ph_info1->height * (ph_info1->bit_count / 8);
	int image_length1 = ph_info1->size_image;
	unsigned char *pdata1 = malloc(image_length1);
	if (!pdata1) {
		printf("err: malloc %d fail!\n", image_length1);
		fclose(fp1);
		return -1;
	}
	fread(pdata1, 1, image_length1, fp1);
	fclose(fp1);

	/* 2. 读取文件2所有内容 */
	fp2 = fopen(fname2, "rb+");
	fread(ph_file2, sizeof(BITMAP_FILE_HEADER_T), 1, fp2);
	fread(ph_info2, sizeof(BITMAP_INFO_HEADER_T), 1, fp2);
	if (!ph_info2->size_image) /* Fix a bug */
		ph_info2->size_image = ((ph_info2->width % 4)
			? ((ph_info2->width / 4) * 4 + 4)
			: ph_info2->width) * ph_info2->height * (ph_info2->bit_count / 8);
	int image_length2 = ph_info1->size_image;
	unsigned char *pdata2 = malloc(image_length2);
	if (!pdata2) {
		printf("err: malloc %d fail!\n", image_length2);
		free(pdata1);
		fclose(fp2);
		return -1;
	}
	fread(pdata2, 1, image_length2, fp2);
	fclose(fp2);

	/* 3. 初始化输出文件的缓存 */
	/* total pixels */
	unsigned char *pdata_diff = malloc(image_length1);
	if (!pdata_diff) {
		printf("err: malloc %d fail!\n", image_length1);
		free(pdata1);
		free(pdata2);
		return -1;
	}
	memset(pdata_diff, 0, image_length1);

	/* only red channel */
	unsigned char *pdata_red = malloc(image_length1);
	if (!pdata_red) {
		printf("err: malloc %d fail!\n", image_length1);
		free(pdata1);
		free(pdata2);
		free(pdata_diff);
		return -1;
	}
	memset(pdata_red, 0, image_length1);

	/* only green channel */
	unsigned char *pdata_green = malloc(image_length1);
	if (!pdata_green) {
		printf("err: malloc %d fail!\n", image_length1);
		free(pdata1);
		free(pdata2);
		free(pdata_diff);
		free(pdata_red);
		return -1;
	}
	memset(pdata_green, 0, image_length1);

	/* only blue channel */
	unsigned char *pdata_blue = malloc(image_length1);
	if (!pdata_blue) {
		printf("err: malloc %d fail!\n", image_length1);
		free(pdata1);
		free(pdata2);
		free(pdata_diff);
		free(pdata_red);
		free(pdata_green);
		return -1;
	}
	memset(pdata_blue, 0, image_length1);

	/* 3. 判断两个图片像素有多少差值 */
	/**
	 * \warning 此处代码只支持24、32bit格式的图片
	 */
	int pixel_unit = ph_info1->bit_count / 8;
	int pixel_total = ph_info1->width * ph_info1->height;
	int pixel_diff_total = 0;
	int pixel_red_diff_total = 0;
	int pixel_green_diff_total = 0;
	int pixel_blue_diff_total = 0;
	int diff_flag;
	int diff_max = 0;
	int diff_min = 0;
	int tmp_left, tmp_right;
	unsigned char diff;
	char signed_diff;
	int index;
	long long int overflow_count = 0; /* 颜色溢出的数量 */
	long long int flip_count = 0; /* 颜色翻转的数量 */
	short int tmp;
	unsigned char tmp2;
	for (i = 0; i < ph_info1->size_image / pixel_unit; i++) {
		diff_flag = 0;
		for (j = 0; j < pixel_unit; j++) {
			index = i * pixel_unit + j;
			diff = 0;
			if (pdata1[index] != pdata2[index]) {
#ifdef USE_SIGNED_CHAR
				if (pdata1[index] > pdata2[index])
					tmp = pdata1[index] - pdata2[index];
				else
					tmp = - (pdata2[index] - pdata1[index]);
				if (tmp > 127) {
					tmp = 127;
					overflow_count++;
				} else if (tmp < -128) {
					tmp = -128;
					overflow_count++;
				}
				signed_diff = tmp;
				diff = (unsigned char)signed_diff;
#else
				if (pdata1[index] < pdata2[index])
					flip_count++;
#	ifdef USE_ABSOLUTE_VALUE
				if (pdata1[index] > pdata2[index])
					diff = pdata1[index] - pdata2[index];
				else
					diff = pdata2[index] - pdata1[index];
#	else
				diff = pdata1[index] - pdata2[index];
#	endif
#endif

				tmp_left = pdata1[index];
				tmp_right = pdata2[index];
				if (tmp_left > tmp_right) {
					if (tmp_left - tmp_right > diff_max)
						diff_max = tmp_left - tmp_right;
				} else {
					if (tmp_left - tmp_right < diff_min)
						diff_min = tmp_left - tmp_right;
				}

				/* 像素颜色存储顺序为蓝绿红B、G、R */
				/* warning: 此处代码处理只对24bit的图片有效！ */
				if (j == 0) {
					pixel_blue_diff_total++;
					pdata_blue[index] = diff;
				} else if (j == 1) {
					pixel_green_diff_total++;
					pdata_green[index] = diff;
				} else if (j == 2) {
					pixel_red_diff_total++;
					pdata_red[index] = diff;
				}
			}
			pdata_diff[index] = diff;
		}

		if (diff > 0)
			pixel_diff_total++;
	}

	/* 4. 打印两张图片的diff信息 */
	printf("======== BMP diff info ========\n");
	printf("总共的有效像素: %d\n", pixel_total);
	printf("有差别的像素: %d, RGB[%d+%d+%d]\n", pixel_diff_total,
			pixel_red_diff_total, pixel_green_diff_total, pixel_blue_diff_total);
	int diff_scale = (pixel_diff_total > 0) ? (pixel_diff_total * 100) / pixel_total : 0;
	printf("差别像素占所有像素的比例 %d\%\n", diff_scale);
	printf("差的最大的一个像素的差值: %d\n", diff_max);
	printf("差的最小的一个像素的差值: %d\n", diff_min);

	long long int total_diff_value = 0;
	for (i = 0; i < image_length1; i++)
		total_diff_value += pdata_diff[i];
	int diff_average = pixel_diff_total > 0 ? total_diff_value / pixel_diff_total / pixel_unit : 0;
	printf("所有有差值的像素平均的差值（在没有设置绝对值或者存储为signed时值无意义）: %d\n", diff_average);
	printf("计算时溢出失真的总三原色数：%d\n", overflow_count);
	printf("计算时翻转失真的总三原色数：%d\n", flip_count);

	/* 5. 写总的差值图片、红绿蓝各通道单独的差值图片 */
	/* 如果没有区别，保存的图片就是纯黑的，如果有区别，保存的图片就能看到灰色、白色或其它颜色 */
	printf("The diff picture name: %s\n", DIFF_FILE_NAME);
	fp_diff = fopen(DIFF_FILE_NAME, "wb+");
	fwrite(&file_header1, sizeof(file_header1), 1, fp_diff);
	fwrite(&info_header1, sizeof(info_header1), 1, fp_diff);
	fwrite(pdata_diff, 1, image_length1, fp_diff);
	fflush(fp_diff);
	fclose(fp_diff);

#ifdef SAVE_RGB_CHANNEL_PICTURE
	/* 只保存红色通道图片 */
	printf("The red channel diff picture name: %s\n", RED_FILE_NAME);
	fp_red = fopen(RED_FILE_NAME, "wb+");
	fwrite(&file_header1, sizeof(file_header1), 1, fp_red);
	fwrite(&info_header1, sizeof(info_header1), 1, fp_red);
	fwrite(pdata_red, 1, image_length1, fp_red);
	fflush(fp_red);
	fclose(fp_red);

	/* 只保存绿色通道图片 */
	printf("The green channel diff picture name: %s\n", GREEN_FILE_NAME);
	fp_green = fopen(GREEN_FILE_NAME, "wb+");
	fwrite(&file_header1, sizeof(file_header1), 1, fp_green);
	fwrite(&info_header1, sizeof(info_header1), 1, fp_green);
	fwrite(pdata_green, image_length1, 1, fp_green);
	fflush(fp_green);
	fclose(fp_green);

	/* 只保存蓝色通道图片 */
	printf("The blue channel diff picture name: %s\n", BLUE_FILE_NAME);
	fp_blue = fopen(BLUE_FILE_NAME, "wb+");
	fwrite(&file_header1, sizeof(file_header1), 1, fp_blue);
	fwrite(&info_header1, sizeof(info_header1), 1, fp_blue);
	fwrite(pdata_blue, image_length1, 1, fp_blue);
	fflush(fp_blue);
	fclose(fp_blue);
#endif

	/* 6. 一定要记得成对释放内存 */
	free(pdata_blue);
	free(pdata_green);
	free(pdata_red);
	free(pdata_diff);
	free(pdata2);
	free(pdata1);

	return ret;
}

static int image_pixel_merge(char *fname1, char *fname2)
{
#define MERGE_FILE_NAME "merge_picture.bmp"
#define MERGE_SIGNED_CHAR_DIFF_FILE
	int ret = 0;
	int i, j;
	FILE *fp1, *fp2, *fp_diff;
	BITMAP_FILE_HEADER_T file_header1, file_header2;
	BITMAP_INFO_HEADER_T info_header1, info_header2;
	BITMAP_FILE_HEADER_T *ph_file1, *ph_file2;
	BITMAP_INFO_HEADER_T *ph_info1, *ph_info2;

	/* 1. 参数检测 */
	if (!fname1 || !fname2) {
		printf("err: one of filename is empty!\n");
		return -1;
	}

	ph_file1 = &file_header1;
	ph_info1 = &info_header1;
	ph_file2 = &file_header2;
	ph_info2 = &info_header2;

	/**
	 * 之前已经判断过了文件长度、BMP格式，此处省略，无需再次进行异常判断和错误处理
	 */

	/* 1. 读取文件1所有内容 */
	fp1 = fopen(fname1, "rb+");
	fread(ph_file1, sizeof(BITMAP_FILE_HEADER_T), 1, fp1);
	fread(ph_info1, sizeof(BITMAP_INFO_HEADER_T), 1, fp1);
	if (!ph_info1->size_image) /* 比如格式工厂转码的BMP文件就有没有总像素值的问题 */
		ph_info1->size_image = ((ph_info1->width % 4)
			? ((ph_info1->width / 4) * 4 + 4)
			: ph_info1->width) * ph_info1->height * (ph_info1->bit_count / 8);
	int image_length1 = ph_info1->size_image;
	unsigned char *pdata1 = malloc(image_length1);
	if (!pdata1) {
		printf("err: malloc %d fail!\n", image_length1);
		fclose(fp1);
		return -1;
	}
	fread(pdata1, 1, image_length1, fp1);
	fclose(fp1);

	/* 2. 读取文件2所有内容 */
	fp2 = fopen(fname2, "rb+");
	fread(ph_file2, sizeof(BITMAP_FILE_HEADER_T), 1, fp2);
	fread(ph_info2, sizeof(BITMAP_INFO_HEADER_T), 1, fp2);
	if (!ph_info2->size_image) /* Fix a bug */
		ph_info2->size_image = ((ph_info2->width % 4)
			? ((ph_info2->width / 4) * 4 + 4)
			: ph_info2->width) * ph_info2->height * (ph_info2->bit_count / 8);
	int image_length2 = ph_info2->size_image;
	unsigned char *pdata2 = malloc(image_length2);
	if (!pdata2) {
		printf("err: malloc %d fail!\n", image_length2);
		free(pdata1);
		fclose(fp2);
		return -1;
	}
	fread(pdata2, 1, image_length2, fp2);
	fclose(fp2);

	/* 3. 初始化输出文件的缓存 */
	/* total pixels */
	unsigned char *pdata_diff = malloc(image_length1);
	if (!pdata_diff) {
		printf("err: malloc %d fail!\n", image_length1);
		free(pdata1);
		free(pdata2);
		return -1;
	}
	memset(pdata_diff, 0, image_length1);

	/* 4. 合并两个图片的像素值 */
	/**
	 * \warning 此处代码只支持24、32bit格式的图片
	 */
	int pixel_unit = ph_info1->bit_count / 8;
	int pixel_total = ph_info1->width * ph_info1->height;
	int index;
	int merge_value;
	int overflow_count = 0;

	for (i = 0; i < ph_info1->size_image / pixel_unit; i++) {
		for (j = 0; j < pixel_unit; j++) {
			/* 像素颜色存储顺序为蓝绿红B、G、R */
			index = i * pixel_unit + j;
#ifdef MERGE_SIGNED_CHAR_DIFF_FILE
			/* 如果要加的第二张图片如果是自己生成的差值图片，则是带符号的 */
			/* unsigned char = unsigned char + char */
			merge_value = (unsigned char)pdata1[index] + (signed char)pdata2[index];
#else
			merge_value = (unsigned char)pdata1[index] + (unsigned char)pdata2[index];
#endif
			if (merge_value > 255) {
				merge_value = 255;
				overflow_count++;
			} else if (merge_value < 0) {
				merge_value = 0;
				overflow_count++;
			}
			pdata_diff[index] = merge_value;
		}
	}

	/* 5. 写合并后的图片 */
	/* 如果没有区别，保存的图片就是纯黑的，如果有区别，保存的图片就能看到灰色、白色或其它颜色 */
	printf("The merge picture name: %s\n", MERGE_FILE_NAME);
	printf("有值溢出的三原色个数%d\n", overflow_count);
	fp_diff = fopen(MERGE_FILE_NAME, "wb+");
	fwrite(&file_header1, sizeof(file_header1), 1, fp_diff);
	fwrite(&info_header1, sizeof(info_header1), 1, fp_diff);
	fwrite(pdata_diff, 1, image_length1, fp_diff);
	fflush(fp_diff);
	fclose(fp_diff);

	/* 6. 一定要记得成对释放内存 */
	free(pdata_diff);
	free(pdata2);
	free(pdata1);

	return ret;
}

static int bmp_2files_parse(int type, char *file1, char *file2)
{
	int ret = 0;
	FILE *fp1, *fp2;
	BITMAP_FILE_HEADER_T file_header1, file_header2;
	BITMAP_INFO_HEADER_T info_header1, info_header2;
	BITMAP_FILE_HEADER_T *ph_file1, *ph_file2;
	BITMAP_INFO_HEADER_T *ph_info1, *ph_info2;

	/* 1. 参数检测 */
	if (!file1 || !file2) {
		printf("file1 %p or file2 %p empty!\n", file1, file2);
		return -1;
	}

	/* 2. 检测图片是否存在，路径是否正确 */
	fp1 = fopen(file1, "rb+");
	if (!fp1) {
		printf("err: filename %s not exist!\n", file1);
		return -1;
	}
	fclose(fp1);

	fp2 = fopen(file2, "rb+");
	if (!fp2) {
		printf("err: filename %s not exist!\n", file2);
		return -1;
	}
	fclose(fp2);

	/* 3. 获取两张图片的信息 */
	ph_file1 = &file_header1;
	ph_info1 = &info_header1;
	ph_file2 = &file_header2;
	ph_info2 = &info_header2;

	printf("\n======== file1 info: ========\n");
	ret = print_bmp_info(file1, &ph_file1, &ph_info1);
	if (ret)
		return ret;
	printf("\n======== file2 info: ========\n");
	ret = print_bmp_info(file2, &ph_file2, &ph_info2);
	if (ret)
		return ret;

	int size;	/* 本结构体占的字节数 */
	int width;	/* 位图宽度，单位是像素 */
	int height;	/* 位图高度，单位像素 */
	short int planes;	/* 目标设备级别，值必须为1 */
	short int bit_count;	/* 每个像素的位数；1：双色，4：16色，8：256色，24：真彩色，32：增强真彩色，现在一般是32位色；
                                   当值为1、4、8时默认颜色表中颜色有2、16、256个；当为24、32时没有颜色表*/
	int compression;	/* 位图压缩类型；当前必须为0：不压缩，其它的还有1：BI_RLE8或BI_RLE4 */
	int size_image;	/* 实际位图像素数据所占的字节数 */
	int x_pels_per_meter;	/* 水平分辨率，每米像素数 */
	int y_pels_per_meter;	/* 垂直分辨率，每米像素数 */
	int clr_used;	/* 位图实际使用的颜色表中的颜色数；现在位图基本上都没有颜色表了，都是真彩色 */
	int clr_important;	/* 位图显示过程中重要的颜色数 */

	/* 4. 比较一下大小、位深是否相同，不相同则没有可比性 */
	printf("\n======== result: ========\n");
	if (ph_info1->width != ph_info2->width) {
		printf("width diff: %d, %d\n", ph_info1->width, ph_info2->width);
		ret = -1;
	}
	if (ph_info1->height != ph_info2->height) {
		printf("height diff: %d, %d\n", ph_info1->height, ph_info2->height);
		ret = -1;
	}
	if (ph_info1->bit_count != ph_info2->bit_count) {
		printf("bit count diff: %d, %d\n", ph_info1->bit_count, ph_info2->bit_count);
		ret = -1;
	}

	if (ret < 0) {
		printf("The two BMP file have diffrent parameters, exit.\n");
		return 0;
	}

	if (type == BMP_COMPARE_2_FILES)
		image_pixel_compare(file1, file2);
	else if (type == BMP_MERGE_2_FILES)
		image_pixel_merge(file1, file2);

	return 0;
}

static int bin_2files_compare(char *file1, char *file2)
{
	int ret = 0;
	FILE *fp1, *fp2;

	/* 1. 参数检测 */
	if (!file1 || !file2) {
		printf("file1 %p or file2 %p empty!\n", file1, file2);
		return -1;
	}

	/* 2. 检测二进制文件是否存在，路径是否正确 */
	fp1 = fopen(file1, "rb+");
	if (!fp1) {
		printf("err: filename %s not exist!\n", file1);
		return -1;
	}
	fclose(fp1);

	fp2 = fopen(file2, "rb+");
	if (!fp2) {
		printf("err: filename %s not exist!\n", file2);
		return -1;
	}
	fclose(fp2);

	/* 3. 读取文件内容 */
	/* 读第一个文件 */
	char *fname = file1;
	FILE *fp = fopen(fname, "rb+"); /* 读写方式打开指定名字的文件，文件不存在则返回空 */
	if (!fp) {
		printf("err: filename %s not exist!\n", fname);
		return -1;
	}

	fseek(fp, 0, SEEK_END);
	int file_len = ftell(fp);
	fseek(fp, 0, SEEK_SET); /* 恢复到起始位置 */
	printf("Bin file length: %d\n", file_len);

	unsigned char *pdata1 = malloc(file_len);
	if (pdata1 == NULL) {
		printf("err: malloc %d fail!\n", file_len);
		fclose(fp);
		return -1;
	}

	ret = fread(pdata1, 1, file_len, fp);
	if (ret != file_len) { /* 如果读取正常，返回值和fread的第3个参数一致 */
		printf("err: read too short: %d(should be %d)!\n", ret, file_len);
		fclose(fp);
		free(pdata1);
		return -1;
	}

	fclose(fp);

	/* 读第二个文件 */
	fname = file2;
	fp = fopen(fname, "rb+"); /* 读写方式打开指定名字的文件，文件不存在则返回空 */
	if (!fp) {
		printf("err: filename %s not exist!\n", fname);
		free(pdata1);
		return -1;
	}

	fseek(fp, 0, SEEK_END);
	int file_len2 = ftell(fp);
	fseek(fp, 0, SEEK_SET); /* 恢复到起始位置 */
	printf("Bin file length: %d\n", file_len2);

	unsigned char *pdata2 = malloc(file_len2);
	if (pdata2 == NULL) {
		printf("err: malloc %d fail!\n", file_len2);
		fclose(fp);
		free(pdata1);
		return -1;
	}

	ret = fread(pdata2, 1, file_len2, fp);
	if (ret != file_len2) { /* 如果读取正常，返回值和fread的第3个参数一致 */
		printf("err: read too short: %d(should be %d)!\n", ret, file_len2);
		fclose(fp);
		free(pdata1);
		free(pdata2);
		return -1;
	}

	fclose(fp);

	/* 4. 比较和存储差别文件内容 */
	if (file_len != file_len2) {
		printf("warning: 2 file lengths %d %d is unequal! use the little length.\n", file_len, file_len2);
	}

	int length = (file_len < file_len2 ? file_len : file_len2);

#define BIN_DIFF_FILE_NAME "diff.bin"
	fp = fopen(BIN_DIFF_FILE_NAME, "wb+");
	if (!fp) {
		printf("err: create filename %s fail!\n", BIN_DIFF_FILE_NAME);
		free(pdata1);
		free(pdata2);
		return -1;
	}
	unsigned char *pdata_diff = malloc (length);
	if (pdata_diff == NULL) {
		printf("err: malloc %d fail!\n", length);
		fclose(fp);
		free(pdata1);
		free(pdata2);
		return -1;
	}
	memset(pdata_diff, 0, length);

	int f1_greater_f2 = 0;
	int f2_greater_f1 = 0;
	int max_value = 0;
	int min_value = 0;
	for (int i = 0; i < length; i++) {
		if (pdata1[i] > pdata2[i]) {
			pdata_diff[i] = pdata1[i] - pdata2[i];
			f1_greater_f2++;
			if (pdata_diff[i] > max_value)
				max_value = pdata_diff[i];
		} else if (pdata1[i] < pdata2[i]) {
			f2_greater_f1++;
			pdata_diff[i] = 0; /* 低阈值截断 */
			if (pdata2[i] - pdata1[i] > max_value)
				min_value = -(pdata2[i] - pdata1[i]);
		} else { /* is pdata1[i] == pdata2[i] */
			pdata_diff[i] = 0;
		}
	}
	fwrite(pdata_diff, 1, length, fp);

	printf("前面文件比后面文件大的字节数：%d\n", f1_greater_f2);
	printf("前面文件小于后面文件的字节数: %d\n", f2_greater_f1);
	printf("有差别的字节的比例：%d\%\n", ((f1_greater_f2 + f2_greater_f1) * 100 ) / length);
	printf("差别最大的值：%d\n", max_value);
	printf("差别最小的值：%d\n", min_value);
	printf("存储的差别文件：%s\n", BIN_DIFF_FILE_NAME);

	free(pdata1);
	free(pdata2);
	free(pdata_diff);

	return 0;
}

/*********************************** 接口函数 **********************************/
/**
 * \brief 1. 手动生成BMP图片；2. 分析BMP图片，打印图片信息；
 *        3. 比较两张BMP图片，生成像素数据差值的图片。
 * \details 使用方法：1、 ./demo 生成一张默认名字的测试用的BMP图片；
 *          2、 ./demo -n filename 生成一张指定名字的BMP图片；
 *          3、 ./demo -c filename1.bmp filename2.bmp 比较两张BMP图片，生成像素数据差值的图片，
 *                     打印不一致像素的比例，打印相差最大的像素数值
 */
int main(int argc, char *argv[])
{
	int ret;
	char *fname = NULL, *fname2 = NULL;
	int type = BMP_CREATE_DEFAULT;

	printf("--------\n");
	printf("- ./demo -h 打印帮助信息\n");
	printf("- ./demo 直接创建名为picture.bmp的灰色图片\n");
	printf("- ./demo filename.bmp 创建自定义名字的灰色图片\n");
	printf("- ./demo -n filename.bmp 创建自定义名字的灰色图片\n");
	printf("- ./demo -p filename.bmp 判断一个BMP文件是否符合标准，并打印图片信息\n");
	printf("- ./demo -c file1.bmp file2.bmp 比较两个BMP文件的异同，打印差别信息，并保存差别像素为diff_picture.bmp文件\n");
	printf("- ./demo -m file1.bmp file2.bmp 合并两张图片，并保存新图片为diff_picture.bmp\n");
	printf("- ./demo -b file1.yuv file2.yuv 比较二进制文件，将差值保存在diff.bin中\n");
	printf("--------\n");

	/* 1. 判断参数，程序总是有第一个默认参数，就是可执行程序的路径和名字 */
	printf("Total arguments: %d\n", argc);
	printf("Arg1 - executable file name: %s\n", argv[0]);

	/* 这是取巧的简单的参数处理，商用的参数处理是一个大模块，需要用额外的代码来实现 */
	if (argc > 1) {
		/* 若str1=str2，则返回零；若str1<str2，则返回负数；若str1>str2，则返回正数 */
		if (strcmp(argv[1], BMP_HELP_STR) == 0) {
			type = BMP_HELP;
		} else if (strcmp(argv[1], BMP_CREATE_NEW) == 0) {
			type = BMP_CREATE_DEFAULT;
		} else if (strcmp(argv[1], BMP_PRINT) == 0) {
			type = BMP_PRINT_INFO;
		} else if (strcmp(argv[1], BMP_COMPARE) == 0) {
			type = BMP_COMPARE_2_FILES;
			if (argc < 4) {
				printf("err: BMP compare must need 2 file name, current %d!\n", argc - 2);
				return 0;
			}
		} else if (strcmp(argv[1], BMP_MERGE) == 0) {
			type = BMP_MERGE_2_FILES;
			if (argc < 4) {
				printf("err: BMP merge must need 2 file name, current %d!\n", argc - 2);
				return 0;
			}
		} else if (strcmp(argv[1], BIN_YUV_COMPARE) == 0) {
			type = BIN_YUV_COMPARE_2_FILES;
			if (argc < 4) {
				printf("err: bin compare must need 2 file name, current %d!\n", argc - 2);
				return 0;
			}
		} else {
			/* 如果第一个参数不是-n -p -c -m，而且只有一个参数，那么那就是一个文件名 */
			if (argc < 3) {
				type = BMP_CREATE_DEFAULT;
				fname = argv[1];
				printf("Option filename: %s\n", fname);
			} else {
				type = BMP_HELP;
			}
		}
		printf("Option type: %d\n", type);
	} else {
		type = BMP_CREATE_DEFAULT;
	}

	if (type == BMP_HELP)
		return 0;

	if (argc > 2) {
		if (type == BMP_CREATE_DEFAULT || type == BMP_PRINT_INFO) {
			fname = argv[2];
			printf("Option filename: %s\n", fname);
		}
	}

	if (argc > 3) {
		if (type == BMP_COMPARE_2_FILES || type == BMP_MERGE_2_FILES
				|| type == BIN_YUV_COMPARE_2_FILES) {
			fname = argv[2];
			fname2 = argv[3];
			printf("2 file name is %s and %s\n", fname, fname2);
		}
	}

	/* 2. 执行操作 */
	if (type == BMP_CREATE_DEFAULT)
		create_test_bmp(GREY, fname);
	else if (type == BMP_PRINT_INFO)
		print_bmp_info(fname, NULL, NULL);
	else if (type == BMP_COMPARE_2_FILES)
		bmp_2files_parse(type, fname, fname2);
	else if (type == BMP_MERGE_2_FILES)
		bmp_2files_parse(type, fname, fname2);
	else if (type == BIN_YUV_COMPARE_2_FILES)
		bin_2files_compare(fname, fname2);

	return 0;
}


